#pragma once

#include <cstdint>
#include <iostream>
#include <string>
#include <string_view>
#include <msgpack.hpp>
#include <vector>
#include "Macros.h"

namespace zeroplus::mq
{

    // Bytes
    class Bytes final
    {
    public:
        [[nodiscard]] static Bytes create(size_t size, uint8_t offset = 0) noexcept;

        [[nodiscard]] static Bytes shallow_copy(uint8_t *data, size_t size) noexcept;

        [[nodiscard]] static Bytes shallow_copy(const uint8_t *data, size_t size) noexcept;

        [[nodiscard]] static Bytes shallow_copy_ptr(void *data) noexcept;

        [[nodiscard]] static Bytes deep_copy(uint8_t *data, size_t size, uint8_t offset = 0) noexcept;

        [[nodiscard]] static Bytes deep_copy(const uint8_t *data, size_t size, uint8_t offset = 0) noexcept;

        [[nodiscard]] static Bytes from_string(const std::string &str, uint8_t offset = 0) noexcept;

        [[nodiscard]] static Bytes from_user_input(const std::string &str, bool *ok = nullptr) noexcept;

        [[nodiscard]] static std::string convert_to_hex_str(const uint8_t *value, size_t size) noexcept;

        [[nodiscard]] static constexpr uint8_t stack_size() noexcept { return kStackSize; }

        Bytes() noexcept;

        Bytes(const Bytes &target) noexcept;

        Bytes(Bytes &&target) noexcept;

        Bytes(const std::initializer_list<uint8_t> &list) noexcept; // NOLINT

        Bytes(const std::vector<uint8_t> &data) noexcept; // NOLINT

        ~Bytes() noexcept;

        Bytes &operator=(const Bytes &target) noexcept;

        Bytes &operator=(Bytes &&target) noexcept;

        Bytes &operator=(const std::vector<uint8_t> &data) noexcept;

        [[nodiscard]] bool operator==(const Bytes &target) const noexcept;

        [[nodiscard]] bool operator!=(const Bytes &target) const noexcept;

        [[nodiscard]] bool operator==(const std::vector<uint8_t> &data) const noexcept;

        [[nodiscard]] bool operator!=(const std::vector<uint8_t> &data) const noexcept;

        [[nodiscard]] uint8_t &operator[](size_t index) noexcept { return data_[index]; }

        [[nodiscard]] const uint8_t &operator[](size_t index) const noexcept { return data_[index]; }

        [[nodiscard]] uint8_t *data() noexcept { return data_; }

        [[nodiscard]] const uint8_t *data() const noexcept { return data_; }

        [[nodiscard]] size_t size() const noexcept { return size_; }

        [[nodiscard]] uint8_t offset() const noexcept { return offset_; }

        [[nodiscard]] bool is_own() const noexcept { return is_own_; }

        [[nodiscard]] bool empty() const noexcept { return data_ == nullptr && size_ == 0; }

        [[nodiscard]] uint8_t *begin() noexcept { return data_; }

        [[nodiscard]] const uint8_t *begin() const noexcept { return data_; }

        [[nodiscard]] uint8_t *end() noexcept { return data_ ? (data_ + size_) : nullptr; }

        [[nodiscard]] const uint8_t *end() const noexcept { return data_ ? (data_ + size_) : nullptr; }

        [[nodiscard]] bool is_ptr() const noexcept { return data_ != nullptr && size_ + offset_ == 0 && !is_own_; }

        template <typename T = void>
        [[nodiscard]] T *to_ptr() const noexcept
        {
            return reinterpret_cast<T *>(data_);
        }

        [[nodiscard]] std::vector<uint8_t> to_raw_data() const noexcept;

        [[nodiscard]] std::string to_string() const noexcept;

        [[nodiscard]] std::string_view to_stringView() const noexcept;

        void clear() noexcept;

        void shallow_copy(const Bytes &bytes) noexcept;

        void deep_copy(const Bytes &bytes) noexcept;

        void deep_copy_self() noexcept;

        friend std::ostream &operator<<(std::ostream &ostream, const Bytes &target) noexcept;

    private:
        enum Type : uint8_t
        {
            kCreate = 0,
            kShallowCopy = 1,
            kDeepCopy = 2,
            kMove = 3,
        };

        Bytes(Type type, uint8_t *data, size_t size, uint8_t offset) noexcept;

        void process_type(Type type, uint8_t *data, size_t size, uint8_t offset, Bytes *tmp = nullptr) noexcept;

        static constexpr uint8_t kStackSize{56};
        uint8_t stack_data_[kStackSize]{0};
        uint8_t *data_{nullptr};
        size_t size_{0};
        uint8_t offset_{0};
        bool is_own_{false};
    };

} // namespace zeroplus::mq
